<?php

namespace app\common\service;


use CURLFile;
use think\facade\Cache;
use think\Log;

class CloudService
{
    private $cloudUrl = '';// 
    private $license;

    public function __construct()
    {
        $this->cloudUrl = config('admin.cloud_domain');
        $this->license = ConfigService::get('system', 'license');
    }


    public function check()
    {
        return success('校验成功');
        $cloudCheck = Cache::get('cloud_check');
        if ($cloudCheck) {
            return $cloudCheck;
        }
        try {

            $response = HttpService::post($this->cloudUrl . '/api/cloud/check', [
                'headers' => [
                    'Accept' => 'application/json',
                    'license' => $this->license
                ],
                'json' => [],
                'timeout' => 1,
            ]);

            if ($response['error'] == 0) {

            }
        } catch (\Exception $e) {
            throw new \Exception('555');
        }
        Cache::set('cloud_check', $response['data'], 3600 * 24);
        return $response['data'];
    }

    /**
     * 将文件分割成块
     *
     * 这个方法将一个文件分割成若干块，每块大小可以通过 $chunkSize 参数指定。默认情况下，块大小为 1MB（1024 * 1024 * 1 字节）。
     * 可以选择指定一个目录来保存块文件，默认为空字符串，表示在当前目录下创建一个以当前时间戳命名的新目录。
     * 如果文件不存在，将抛出 '文件不存在' 异常。如果无法打开文件，将抛出 '无法打开文件' 异常。
     *
     * @param string $sourceFilePath 要分割的源文件路径
     * @param string $chunkDir 保存块文件的目录（可选）
     * @param int $chunkSize 每个块的大小（以字节为单位）（可选）
     * @return array 包含块文件路径的数组
     * @throws Exception 如果文件不存在或无法打开文件
     */
    public function chunkFile($sourceFilePath, $chunkDir = '', $chunkSize = 1024 * 1024 * 1)
    {

        // 确保文件存在
        if (file_exists($sourceFilePath)) {
            // 获取文件总大小
            $fileSize = filesize($sourceFilePath);
            // 计算需要的块数量
            $numberOfChunks = ceil($fileSize / $chunkSize);

            // 打开原文件
            $handle = fopen($sourceFilePath, 'rb');

            // 确保文件句柄有效
            if ($handle) {
                // 创建分块目录（如果不存在）
                $chunkDir = public_path('part/' . time() . $chunkDir);
                if (!file_exists($chunkDir)) {
                    mkdir($chunkDir, 0777, true);
                }
                // 存放分片的数组
                $chunkDirArr = [];
                // 遍历每个块
                for ($i = 0; $i < $numberOfChunks; $i++) {
                    // 读取文件块
                    $chunkContent = fread($handle, $chunkSize);

                    // 创建分块文件路径
                    $chunkFilePath = $chunkDir . "chunk_{$i}.part";

                    // 打开分块文件
                    $writeHandle = fopen($chunkFilePath, 'wb');

                    // 写入分块内容
                    fwrite($writeHandle, $chunkContent);

                    // 关闭分块文件句柄
                    fclose($writeHandle);
                    $chunkDirArr[] = $chunkFilePath;
                }

                // 关闭原文件句柄
                fclose($handle);
                return $chunkDirArr;
            } else {
                new \Exception('无法打开文件');
            }
        } else {
            new \Exception('文件不存在');
        }
    }

    /**
     * 合并文件的方法
     *
     * 这个方法接受一个文件路径数组和一个目标合并文件路径作为参数。它会检查目标目录是否存在，
     * 打开合并文件，遍历文件数组，将每个文件的内容读入并写入合并文件。如果写入操作失败，
     * 它会抛出异常并关闭合并文件。如果选择在合并后删除原文件，你可以启用注释中提供的代码。
     * @param array $files 要合并的文件路径数组
     * @param string $mergedFilePath 合并后的文件路径
     * @return void
     * @throws Exception 如果目标目录不存在或合并文件无法打开，如果任何一个文件不存在或写入合并文件失败
     */
    public function mergeFiles($files, $mergedFilePath)
    {
        // 确保输出路径的目录存在
        $directory = dirname($mergedFilePath);
        if (!is_dir($directory)) {
            throw new \Exception("Directory does not exist: {$directory}");
        }

        // 打开输出文件
        $mergedFile = fopen($mergedFilePath, 'wb');

        if (!$mergedFile) {
            throw new \Exception("Cannot open file for writing: {$mergedFilePath}");
        }

        // 遍历文件数组
        foreach ($files as $file) {
            // 检查文件是否存在
            if (!file_exists($file)) {
                fclose($mergedFile);
                throw new \Exception("File does not exist: {$file}");
            }

            // 读取文件内容
            $content = file_get_contents($file);

            // 将内容写入合并文件
            if (false === fwrite($mergedFile, $content)) {
                fclose($mergedFile);
                throw new \Exception("Failed to write to merged file.");
            }

            // 可选：删除已合并的文件
            unlink($file);
        }

        // 关闭合并文件
        fclose($mergedFile);

        return $mergedFilePath;
    }


    public function chunkUpload($sourceFile, $clientId)
    {
        $chunkFileArr = $this->chunkFile($sourceFile);
        // foreach ($chunkFileArr as $key => $chunkFile) {
        $chunkSize = 1 * 1024 * 500; // 例如，0.5MB每个分片
        $fileResource = fopen($sourceFile, 'rb');

        if (!$fileResource) {
            new \Exception('无法打开源文件。');
        }
        $dir = public_path() . "backup/part/";
        if (!is_dir($dir) && !mkdir($dir, 0777, true)) {
            new \Exception('目录创建失败');
        }
        $chunkIndex = 0;
        while (!feof($fileResource)) {
            $chunkContent = fread($fileResource, $chunkSize);

            $chunkFile = $dir . "chunk_{$chunkIndex}.part";
            //file_put_contents($chunkFile, $chunkContent);
            $file = fopen($chunkFile, 'w');

            if ($file === false) {
                new \Exception('创建文件失败');
            }

            // 写入内容
            $a = fwrite($file, $chunkContent);
            // 关闭文件
            fclose($file);
            $chunks[] = $chunkFile; // 记录分片文件路径
            $chunksFileName[] = "chunk_{$chunkIndex}.part"; // 分片名称
            $chunkIndex++;
        }

        fclose($fileResource);


        $uploadUrl = $this->cloudUrl . '/api/cloud/upload';


        foreach ($chunks as $index => $chunkFile) {
            $curl = curl_init();
            $data = array('file' => new CURLFile($chunkFile), 'client_id' => $clientId, 'chunks' => $chunksFileName);
            // 处理数组：curl 不支持直接发送数组，因此需要对数组进行处理
            foreach ($data['chunks'] as $key => $value) {
                $data["chunks[$key]"] = $value;
            }
            unset($data['chunks']);
            $headerArray = [
                'Content-Type: multipart/form-data'
            ];
            curl_setopt($curl, CURLOPT_URL, $uploadUrl);
            curl_setopt($curl, CURLOPT_POST, true);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headerArray);

            $info = curl_getinfo($curl);
            $response = curl_exec($curl);
            if (curl_errno($curl)) {
                // 错误处理
                new \Exception('cURL 错误: ' . curl_error($curl));

            }
            curl_close($curl);
            // var_dump($response);
            // 检查服务器响应，确定分片是否上传成功
            if (!$response) {
                // 处理上传失败的情况
                new \Exception('上传失败');
            }

            // 删除已上传的分片文件
            // unlink($chunkFile);
        }

        return $clientId;
        // }
    }

    /**
     * 接收分片上传文件
     *
     * 这个方法处理文件的分片上传。它首先检查是否存在上传的文件，如果有，它将文件移动到指定的目录，
     * 并根据上传的分片索引命名。然后，它检查所有分片是否都已上传，使用 RedisMsgService 记录上传状态，
     * 并合并所有分片至一个最终文件。合并后，它记录合并成功的消息，并准备开始编译。最后，如果需要，
     * 它可以解压合并后的文件到指定目录。
     *
     * @param array $chunksArr 分片数组，包含每个分片的信息
     * @param string $clientId 客户端ID，用于记录消息
     * @return string 合并后的文件路径，或其他你需要返回的信息
     */
    public function receiveChunkUpload($chunksArr, $clientId)
    {
        $chunkIndex = isset($_FILES['file']['name']) ? pathinfo($_FILES['file']['name'], PATHINFO_FILENAME) : 0;
        $dir = public_path() . "uploads/part/" . $clientId . '/';
        if (!file_exists($dir)) {
            mkdir($dir, 0777, true);
        }
        $filePath = $dir . $chunkIndex . '.part';

        if (move_uploaded_file($_FILES['file']['tmp_name'], $filePath)) {
//            (new RedisMsgService())->record($clientId, 'uploadSuccess', "分片 {$chunkIndex} 上传成功");
            Log::info("分片 {$chunkIndex} 上传成功");

        } else {
            new \Exception('上传失败');
        }

        $finalFile = $dir . 'largefile.zip';
        $fileResource = fopen($finalFile, 'wb');

        $chunksPath = $dir; // 分片保存的目录
        $chunks = glob($chunksPath . '*.part');
        if (count($chunks) == count($chunksArr)) {
            foreach ($chunks as $chunk) {
                fwrite($fileResource, file_get_contents($chunk));
                unlink($chunk); // 删除分片文件
            }

            fclose($fileResource);
//            (new RedisMsgService())->record($clientId, 'uploadSuccess', "所有分片合并成功");
            Log::info("所有分片合并成功");
            // unzip_file($finalFile, $dir . 'web');


//            (new RedisMsgService())->record($clientId, 'startBulid', "准备编译");
            Log::info("准备编译");

            return $finalFile;

        }
    }


    public function setProcessLog($clientId, $msg = '', $action = '')
    {
        return true;
        try {
            // 进度回调函数
            $res = HttpService::post(
                config('admin.cloud_domain') . '/api/cloud/setProcessLog',
                [
                    'headers' => [
                        'Accept' => 'application/json',
                        'license' => $this->license
                    ],
                    'json' => ['client_id' => $clientId, 'msg' => $msg, 'action' => $action]
                ]
            );
        } catch (\Exception $e) {

        }

    }

    /**
     * 分片下载
     *
     * 这个方法接受一个源文件路径和一个目标文件路径作为参数。它会检查目标文件是否存在，
     * 如果存在，则删除它。然后，它会计算源文件的总大小，并计算需要的块数量。
     **/
    public function chunkDownload($chunkFilePaths, $clientId)
    {
        set_time_limit(0);
        $chunkSaveFilePaths = [];// 存放分片文件路径目录
        // 文件保存路径
        $dir = public_path() . 'part/' . $clientId . '/';
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }

        foreach ($chunkFilePaths as $key => $chunkFile) {
            // 目标URL
            $url = $chunkFile;


            $filePath = $dir . 'chunk_' . $key . '.part';
            $chunkSaveFilePaths[] = $filePath;
            // 初始化cURL会话
            $ch = curl_init($url);

            // 设置cURL选项
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); // 不通过PHP输出数据，直接写入文件
            curl_setopt($ch, CURLOPT_HEADER, 0); // 不需要HTTP头
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 允许重定向
            curl_setopt($ch, CURLOPT_MAXREDIRS, 10); // 设置最大重定向次数
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 如果是HTTPS，不验证SSL证书
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // 不验证SSL主机
            curl_setopt($ch, CURLOPT_TIMEOUT, 0); // 设置超时时间（0 表示无超时）
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
            // curl_setopt($ch, CURLOPT_NOPROGRESS, false); // 允许进度回调
            // curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, 'progressCallback'); // 进度回调函数
            curl_setopt($ch, CURLOPT_FILE, fopen($filePath, 'wb')); // 设置文件句柄
            // 执行cURL会话
            if (curl_exec($ch)) {

                // 关闭cURL会话
                curl_close($ch);
                // unzip_file($filePath, public_path() . 'super');
                // 进度回调函数

                $this->setProcessLog($clientId, "分片chunk_{$key}.part下载成功");
                // echo "文件下载成功，保存在：$filePath\n";
                // return success('success', ['client_id' => $clientId, 'file' => $filePath]);
            } else {
                // 关闭cURL会话
                curl_close($ch);
                // return error('error', ['client_id' => $clientId, 'file' => $filePath]);
            }


        }
        $this->mergeFiles($chunkSaveFilePaths, $dir . 'merge.zip');
        return $dir . 'merge.zip';

    }


}